#ifndef XDISPLAY_H_
#define XDISPLAY_H_

#include <X11/Xlib.h> 

#include <X11/Xatom.h>
#include <assert.h>
#include <string>
#include <iostream>
#include "PtrMap.h"

using namespace std;
class SelectionEventListener {
public:
	virtual ~SelectionEventListener() {};
	virtual void handleSelectionRequestEvent(XSelectionRequestEvent *event) =0;
	virtual void handleSelectionNotifyEvent(XSelectionEvent * event) =0;
	virtual void handleSelectionClearEvent(XSelectionClearEvent * event) =0;
};

class XDisplay
{

public:
	XDisplay(): display_(XOpenDisplay(NULL)), targetsAtom_(XA_ATOM), map_(){
		assert(this->display_);
		targetsAtom_ = this->getAtom("TARGETS");
	}
	
	
	
	virtual ~XDisplay();
	
	
	int xSendEvent (Window window, XEvent* respond) {
		int result = XSendEvent(this->display_, window, 0, 0, respond);
		assert(result >= 0);
		return result; 
	}
	operator Display*(){
		printf("zwracam %p\n", (void*)display_);
		return display_;
	}
	
	void selectInput(Window window, long int mask) {
		int result = XSelectInput(display_, window, mask);
		assert(result >= 0);
	}
	Atom getAtom(const string& name) const;
	
	std::string *getAtomName(Atom atom) {
		char * data = XGetAtomName(display_, atom);
		std::string *res = new std::string(data);
		XFree(data);
		return res;
	}
	void sendEvent(Window target, XEvent *event) {
		XSendEvent(display_, target, 0, 0, event);
	}
	std::string * getWindowProperty(Window window, Atom property);
	
	void flush() {
		XFlush(this->display_);
	}
	
	Display * getRawHandler() {
		return this->display_;
	}
	
	XEvent* getNextEvent() __attribute_malloc__ {
		XEvent* event = (XEvent*) calloc(1, sizeof(XEvent));
		XNextEvent(this->display_, event);
		return event;
	}
	
	XEvent* peekEvent() __attribute_malloc__ {
		XEvent* event = (XEvent*) calloc(1, sizeof(XEvent));
		XPeekEvent(this->display_, event);
		return event;
	}
	
	std::string * getWindowName(Window window) {
		char * name = NULL;
		std::string * result;
		XFetchName(this->display_, window, &name);
		if (name != NULL) {
			result = new  std::string(name);
			XFree(name);
		} else {
			result = new  std::string();
		}
		return result;
	}
	
	Window getSelectionOwner(Atom selection) {
		return XGetSelectionOwner(this->display_, selection);	
	}
	
	void setSelectionOwner(Atom selection, Window owner) {
		XSetSelectionOwner (this->display_, selection, owner, CurrentTime);
		this->flush();
	}
	
	void convertSelection(Atom selection, Window target, Atom property) {
		XConvertSelection(this->display_, selection, XA_STRING, property, target, CurrentTime);
		this->flush();
	}
	
	void setWindowProperty(Window targetWindow, Atom targetProperty, const string* value) { 
		XChangeProperty(this->display_, targetWindow, targetProperty, XA_STRING, 8,
					PropModeReplace, (unsigned char*)value->c_str(), value->length());
		this->flush();
	}
	
	void setWindowProperty(Window targetWindow, Atom targetProperty) { 
		Atom a = XA_STRING;
		XChangeProperty(this->display_, targetWindow, targetProperty, XA_ATOM, 32,
					PropModeReplace, (const unsigned char*)&a, 1);
		this->flush();
	}
	
	
	void registerHandler(Window window, const SelectionEventListener *listener) {
		map_.put(window, const_cast<SelectionEventListener *>(listener));
	}
	SelectionEventListener * getListener(Window window, XEvent * event) {
		
		SelectionEventListener *listener = map_.get(window);
		if (listener != NULL) {
			//std::cout << "- dispatching to handler: " <<listener << " for window" << window << endl;
		} else {
			std::cout << "Recived event: " << event->type << "-";
			std::cout << " but no handler found for window" << window << endl;
		}
		return listener;
	}
	
	

	
	void main() {
		XEvent event;
		XSelectionRequestEvent *eventRequest = &event.xselectionrequest;
		XSelectionEvent * eventNotify = &event.xselection;
		XSelectionClearEvent * eventClear = &event.xselectionclear;
		SelectionEventListener *listener;
		//std::cout << "entering main event loop" << std::endl;
		//std::cout << "waiting for events from display " << display_ << std::endl;
		while(1) {
			XNextEvent(display_, &event);
			switch(event.type) {
				case SelectionRequest:
					/*if (eventRequest->target == XA_TARGETS) {
						break; //let's ignore it :/ FIXME: send back proepr ATOM list
					}*/
					listener = getListener(eventRequest->owner, &event);
					if (listener != NULL) {
						listener->handleSelectionRequestEvent(eventRequest);
					}
					break;
				case SelectionNotify:
					listener = getListener(eventNotify->requestor, &event);		
					if (listener != NULL) {
						listener->handleSelectionNotifyEvent(eventNotify);
					}
					break;
				case SelectionClear:
					listener = getListener(eventClear->window, &event);		
					if (listener != NULL) {
						listener->handleSelectionClearEvent(eventClear);
					}
					break;
				break;
				case Expose:
				case ConfigureNotify:
				case ReparentNotify:
				case MapNotify:
					break;	/* silent ignore */
				default: 
					//std::cout << "Recived unknown event: " << event.type << endl;
					break;
			}
		}
	}
	Atom getTargetsAtom() {
		return targetsAtom_;
	}
private:
	Display *display_;
	Atom targetsAtom_;
	PtrMap<Window, SelectionEventListener> map_;
	/* dont'allow coping */
	XDisplay(const XDisplay&);
	void operator=(const XDisplay&);
};

#endif /*XDISPLAY_H_*/
